//
// Created by LiuYou on 2021/5/7.
//

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cassert>
#include <sys/poll.h>
#include <cerrno>
#include <unistd.h>
#include "ClientData.hpp"

using C::ClientData;

int main(int argc, char** argv) {
    if (argc < 3) {
        std::fprintf(stderr, "Usage: %s IP Port\n", basename(argv[0]));
        return -1;
    }
    const char* const ip = argv[1];
    int port = std::atoi(argv[2]);

    sockaddr_in serverSockAddrIn{
            .sin_family=PF_INET,
            .sin_port=htons(port),
            {.s_addr=0}
    };
    inet_pton(AF_INET, ip, &serverSockAddrIn.sin_addr);

    int listenFD = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenFD >= 0);

    int ret = bind(listenFD, (sockaddr*) &serverSockAddrIn, sizeof(serverSockAddrIn));
    assert(ret != -1);

    ret = listen(listenFD, 5);
    assert(ret != -1);

    const int FD_LIMIT = 65535;
    ClientData* clientsData = new ClientData[FD_LIMIT]();
    const int USER_LIMIT = 5;
    pollfd pollFDS[USER_LIMIT + 1];
    for (int i = 1; i < USER_LIMIT; ++i) {
        pollFDS[i].fd = -1;
        pollFDS[i].events = 0;
    }
    pollFDS[0].fd = listenFD;
    pollFDS[0].events = POLLIN | POLLERR;
    pollFDS[0].revents = 0;

    int clientsCounter{};
    for (;;) {
        int ret = poll(pollFDS, clientsCounter + 1, -1);
        if (ret < 0) {
            std::fprintf(stderr, "poll failure\n");
            break;
        }

        for (int i = 0; i < clientsCounter + 1; ++i) {

            if ((pollFDS[i].fd == listenFD) && (pollFDS[i].revents & POLLIN)) {
                // 如果poll监听的文件是listenFD, 而且有内容写入。(即有新的客户端连接)
                sockaddr_in clientSockAddrIn{};
                socklen_t clientSockAddrInLength = sizeof(clientSockAddrIn);
                int connectionFD = accept(listenFD, reinterpret_cast<sockaddr*>(&clientSockAddrIn),
                                          &clientSockAddrInLength);
                if (connectionFD < 0) {
                    std::fprintf(stderr, "errno is: %d\n", errno);
                    continue;
                }

                // TODO condition
                if (clientsCounter > USER_LIMIT) {
                    const char* info = "too many users(clients)\n";
                    std::fprintf(stderr, "%s", info);
                    // TODO send给谁
                    send(connectionFD, info, std::strlen(info), 0);
                    close(connectionFD);
                    continue;
                }

                ++clientsCounter;
                clientsData[connectionFD].setSockAddrIn(clientSockAddrIn);
                // 设置为非阻塞, 没有内容(读不到socket内容)我就走了。执行后续的代码。
                C::setNonBlocking(connectionFD);
                // 如果是第一次执行到这里, 就是把第一个连接到Server的Client的Server的socket文件放到poll监听队列中,
                // 第一次的话就是pollFDS[1].fd = connectionFD; 而pollFDS[0]在监听listenFD。
                pollFDS[clientsCounter].fd = connectionFD;
                pollFDS[clientsCounter].events = POLLIN | POLLRDHUP | POLLERR;
                pollFDS[clientsCounter].revents = 0;
                std::fprintf(stdout, "comes a new user, now have %d users\n", clientsCounter);

            } else if (pollFDS[i].revents & POLLERR) {
                // TODO 没写这个逻辑

            } else if (pollFDS[i].revents & POLLRDHUP) {
                // 如果客户端关闭连接, 则服务器也关闭对应的连接, 并将连入到Server的Client总数减一。
                // TODO
                close(pollFDS[i].fd);
                // 在pollFDS数组中把最末尾的pollFDS的connectionFD来放到刚刚删除的这里。
                pollFDS[i] = pollFDS[clientsCounter];
                // 并且将ClientsData[clientsCounter]的数据也放到刚刚离开Server连接的那个客户端数据索引上。
                clientsData[pollFDS[i].fd] = clientsData[pollFDS[clientsCounter].fd];
                // 现在此时将刚刚离开的连接的pollFDS监听数组中移除了, 而且ClientData数组中保存的它数据也移除了。

                // TODO
                // 这个--i我觉得没有必要。
                // 但是经过测试, 没有是不行的！
                // 不对反转了卧槽, 我又编译了一遍, 没有--i也是可以的！
                // 在下面我想通了。要加上--i。
                --i;
                --clientsCounter;
                std::fprintf(stdout, "a user(client) left\n");
            } else if (pollFDS[i].revents & POLLIN) {
                // 有数据了, 那就读
                std::memset(clientsData[pollFDS[i].fd].buffer, '\0', C::getBufferSize());
                ssize_t ret = recv(pollFDS[i].fd, clientsData[pollFDS[i].fd].buffer, C::getBufferSize() - 1, 0);
                // TODO
                // 这里的from %d 最后改为具体的名字。
                std::fprintf(stdout,
                             "get %d bytes of client data: %s from %d\n",
                             ret,
                             clientsData[pollFDS[i].fd].buffer,
                             pollFDS[i].fd);
                if (ret < 0) {
                    // 如果读操作出错, 则关闭连接
                    // TODO 这里和上面的client连接断开是一样的操作
                    //  不过这个errno现在不懂。
                    if (errno != EAGAIN) {
                        close(pollFDS[i].fd);
                        clientsData[pollFDS[i].fd] = clientsData[pollFDS[clientsCounter].fd];
                        pollFDS[i] = pollFDS[clientsCounter];
                        // TODO
                        // 同样, 我觉得--i不是很有必要
                        // 不过就在刚刚我想通了。--i;后然后在for中又++i, 此时poll轮询当前的connectionFD。
                        // 如果没有--i也是可以的, 就是下次轮询到这个connectionFD时要等一轮。
                        // 我想通了现在。所以还是加上吧。
                        --i;
                        --clientsCounter;
                    }
                } else if (ret == 0) {
                    // 没有对这个逻辑的描述
                    // 其实是可以删掉的。
                } else {
                    // 如何Server读到了某个Client的数据, 那么通过Server中连接其它Client的connectionFD准备写数据
                    // 这里的condition中的=号是一定要有的。因为要通知所有连接Server的客户端。
                    for (int j = 1; j <= clientsCounter; ++j) {
                        // 除了当前的connectionFD无需通知外
                        if (pollFDS[j].fd == pollFDS[i].fd) {
                            continue;
                        }

                        /*
                         * 暂时取消其他客户连接的可读事件
                         * 这句代码应该写成 fds[j].events &= ~POLLIN;否则所有选项都将置位，程序虽然能正常运行，但不是作者本意。
                         */
                        // TODO https://bbs.csdn.net/topics/396849194?list=70278186
                        // TODO https://blog.csdn.net/weixin_38514703/article/details/106254010
                        // TODO https://blog.csdn.net/gatieme/article/details/50978320
                        pollFDS[j].events |= ~POLLIN;
                        pollFDS[j].events |= POLLOUT;
                        // 将当前的connectionFD的内容全部发送给其它连接到Server的Client。
                        clientsData[pollFDS[j].fd].setWriteBuffer(clientsData[pollFDS[i].fd].getBuffer());
                    }
                }
            } else if (pollFDS[i].revents & POLLOUT) {
                // TODO 这个逻辑分支不知道在干什么
                int connectFD = pollFDS[i].fd;
                if (!clientsData[connectFD].getWriteBuffer()) {
                    continue;
                }
                ssize_t ret = send(connectFD, clientsData[connectFD].getWriteBuffer(),
                                   std::strlen(clientsData[connectFD].getWriteBuffer()), 0);
                clientsData[connectFD].setWriteBuffer(nullptr);
                // TODO
                // 写完数据后需要重新注册pollFDS[i]上的可读事件
                pollFDS[i].events |= ~POLLOUT;
                pollFDS[i].events |= POLLIN;
            }
        }
    }

    delete[] clientsData;
    close(listenFD);

    return 0;
}